Database part
Geo data and April data collections
- Note: This chunk needn’t run again.
# Set Twitter developer account
create_token(app='MSSP-An-Auxiliary-Tool',
consumer_key='ORvbA3CEOP06hi9MHfz7yknwV',
consumer_secret='nAy2PRkiV4AYZ0NvHAF6Iw0IBFrttWMKTuxXbUWN4bcZnMpTQR',
access_token='1328377313562509313-j1iSFuJLLo3FL768jdnHKe1fzmcWnS',
access_secret='oJIhGoThBNSBSMLQBo3AxS5kcLrqq8sCjx6OIPX9NRmPT')
# Select all tweets in April 2020
if(F){
paste("CREATE TABLE CoronavirusTweets",
" AS SELECT * FROM CoronavirusTweetsCsv",
" WHERE (strftime('%Y-%m-%d %H:%M:%S',created_at)>=",
"strftime('%Y-%m-%d %H:%M:%S','2020-03-29 00:00:00'))",
" AND (strftime('%Y-%m-%d %H:%M:%S',created_at)<=",
"strftime('%Y-%m-%d %H:%M:%S','2020-04-29 23:59:59'))",sep='')%>%
dbSendQuery(conn,.)
April_tweet=paste("SELECT Tweet_ID FROM CoronavirusTweets",sep='')%>%
dbGetQuery(conn,.)
for(i in 1:ceiling(nrow(April_tweet)/90000)) {
rl=rate_limit("lookup_statuses")
if(rl%>%select(remaining)!=900){
rl%>%select(reset)*60%>%ceiling()%>%Sys.sleep()
}
april_tweet=lookup_statuses(April_tweet$Tweet_ID[(900*i):nrow(April_tweet)])
if(i==1){April_tweet=april_tweet}else{April_tweet=rbind(April_tweet,april_tweet)}
}
April_tweet%>%
select(status_id,user_id,screen_name,created_at,text,is_quote,
is_retweet,favourites_count,retweet_count,followers_count,
friends_count,lang)%>%
dbWriteTable(conn,'CoronavirusTweets',.)
}
# Select all tweets with geo information from 202001 to 202011
if(F){
paste("CREATE TABLE CoronavirusTweetsGeo",
" AS SELECT * FROM CoronavirusTweetsCsv",
" WHERE Geolocation_coordinate='YES'",sep='')%>%
dbSendQuery(conn,.)
Geo_tweet=paste("SELECT Tweet_ID FROM CoronavirusTweetsGeo",sep='')%>%
dbGetQuery(conn,.)
for(i in 1:ceiling(nrow(Geo_tweet)/90000)) {
rl=rate_limit("lookup_statuses")
if(rl%>%select(remaining)!=900){
rl%>%select(reset)*60%>%ceiling()%>%Sys.sleep()
}
geo=lookup_statuses(Geo_tweet$Tweet_ID[(900*i):nrow(Geo_tweet)])
if(i==1){Geo=geo}else{Geo=rbind(Geo,geo)}
}
lat_lng(Geo)%>%
select(status_id,user_id,screen_name,created_at,text,is_quote,
is_retweet,favourites_count,retweet_count,followers_count,
friends_count,lang,place_full_name,place_type,country_code,
place_name,country,lat,lng)%>%
dbWriteTable(conn,'CoronavirusTweetsGeo',.)
}
# Delete initial collection of covid tweets csv files table
#"DROP TABLE CoronavirusTweetsCsv"%>%
# dbSendQuery(conn,.)
dbDisconnect(conn)
Get data function
getTwitterData=function(conn,geoinfo=T,keywords=NULL,
period=c('2020-03-29 00:00:00','2020-04-30 23:59:59')){
# Select table of database according to 'geoinfo'
if(geoinfo){
geoinfo_query='CoronavirusTweetsGeo'
}
else{
geoinfo_query='CoronavirusTweets'
}
# Add keywords conditions according to 'keywords'
if(length(keywords==0)){
keywords_query=''
}
else{
for(i in 1:length(keywords)){
if(i==1){
keywords_query=paste(" ((text LIKE '%",keywords[i],"%')",sep="")
}
else{
keywords_query=keywords_query%>%
paste("OR (text LIKE '%",keywords[i],"%')",sep="")
}
}
keywords_query=paste(keywords_query,") ",sep="")
}
# Add period conditions according to 'period'
if(length(period)!=2){
period_query=''
}
else{
period_query=paste(" (strftime('%Y-%m-%d %H:%M:%S',created_at)>=",
"strftime('%Y-%m-%d %H:%M:%S','",period[1],"') ",
"AND strftime('%Y-%m-%d %H:%M:%S',created_at)<=",
"strftime('%Y-%m-%d %H:%M:%S','",period[2],"')) ",
sep="")
}
# Write SQL
if(period_query==''){
if(keywords_query==''){
query=paste("SELECT * FROM ",geoinfo_query,
" ORDER BY status_id",sep="")
}
else{
query=paste("SELECT * FROM ",geoinfo_query," ",
"WHERE",keywords_query,
" ORDER BY status_id",sep="")
}
}
else{
if(keywords_query==''){
query=paste("SELECT * FROM ",geoinfo_query," ",
"WHERE",period_query,
" ORDER BY status_id",sep="")
}
else{
query=paste("SELECT * FROM ",geoinfo_query," ",
"WHERE",period_query,
"AND",keywords_query,
" ORDER BY status_id",sep="")
}
}
# Obtain Data
dbGetQuery(conn,query)%>%mutate(created_at=ymd_hms(created_at))
}
getTwitterTrend=function(conn,geoinfo=T,trend='day',keywords=NULL,
period=c('2020-03-29 00:00:00','2020-04-30 23:59:59')){
# Add trend cconditions according to 'trend'
if(trend=='day'){
trend_query=c("'%Y-%m-%d'","date")
}
else{
if(trend=='week'){
trend_query=c("'%W'","week")
}
else{
if(trend=='month'){
trend_query=c("'%m'","month")
}
else{
stop("The trend can only be 'day', 'week' or 'month'.")
}
}
}
# Select table of database according to 'geoinfo'
if(geoinfo){
geoinfo_query=paste("SELECT strftime(",trend_query[1],
",created_at) AS ",trend_query[2],", ",
"count(*) AS number, place_name, country, ",
"country_code FROM CoronavirusTweetsGeo ", sep="")
group_query=paste(" GROUP BY strftime(",trend_query[1],
",created_at),place_name,country_code",sep="")
}
else{
geoinfo_query=paste("SELECT strftime(",trend_query[1],
",created_at) AS ",trend_query[2],", ",
"count(*) AS number FROM CoronavirusTweets",sep="")
group_query=paste(" GROUP BY strftime(",trend_query[1],
",created_at)",sep="")
}
# Add keywords conditions according to 'keywords'
if(length(keywords==0)){
keywords_query=''
}
else{
for(i in 1:length(keywords)){
if(i==1){
keywords_query=paste(" ((text LIKE '%",keywords[i],"%')",sep="")
}
else{
keywords_query=keywords_query%>%
paste("OR (text LIKE '%",keywords[i],"%')",sep="")
}
}
keywords_query=paste(keywords_query,") ",sep="")
}
# Add period conditions according to 'period'
if(length(period)!=2){
period_query=''
}
else{
period_query=paste(" (strftime('%Y-%m-%d %H:%M:%S',created_at)>=",
"strftime('%Y-%m-%d %H:%M:%S','",period[1],"') ",
"AND strftime('%Y-%m-%d %H:%M:%S',created_at)<=",
"strftime('%Y-%m-%d %H:%M:%S','",period[2],"')) ",
sep="")
}
# Write SQL
if(period_query==''){
if(keywords_query==''){
query=paste(geoinfo_query,group_query,sep="")
}
else{
query=paste(geoinfo_query," WHERE",keywords_query,group_query,sep="")
}
}
else{
if(keywords_query==''){
query=paste(geoinfo_query," WHERE",period_query,group_query,sep="")
}
else{
query=paste(geoinfo_query," WHERE",period_query,"AND",keywords_query,
group_query,sep="")
}
}
# Obtain Data
dbGetQuery(conn,query)
}
Examples
# connect to data base
conn=dbConnect(SQLite(),dbpath)
# get twitter data with geo information
tweetsGeo=getTwitterData(conn,period = NULL)
# get twitter montly data with geo information
tweetsMonthlyGeo=getTwitterTrend(conn,trend='month',period=NULL)
# get twitter data with giving keywords
tweets=getTwitterData(conn,geoinfo = F,keywords = c('mask','N95','口罩'))
# get twitter daily trends giving keywords
tweetsDaily=getTwitterTrend(conn,geoinfo = F,keywords = c('mask','N95','口罩'))
# disconnect data base
dbDisconnect(conn)
The sentiment analysis part
# For English tweets in United States
getEnScore <- function(DF, keyword){
DFsub <- DF %>%
filter(lang=="en"&country_code=="US")
DFsub$date <- gsub("T.*", "", DFsub$created_at)
wordDF <- DFsub %>%
unnest_tokens(word, text) %>%
anti_join(stop_words)
scoreDF <- wordDF %>%
inner_join(get_sentiments("bing")) %>%
count(status_id, sentiment) %>%
spread(sentiment, n)
scoreDF[is.na(scoreDF)] <- 0
scoreDF <- scoreDF %>%
mutate(sentiment_score=(positive-negative)/(positive+negative))
tweetScoreDF <- right_join(DFsub, scoreDF, by="status_id")
if(length(keyword)!=1){
keyword <- paste(keyword, collapse="|")
}
keywordDF <- tweetScoreDF %>%
filter(grepl(keyword, text))
return(
keywordDF %>%
group_by(date) %>%
summarize(overall_sentiment=mean(sentiment_score))
)
}
# For all languages
# Create sentiment lexicon dictionary
# https://www.kaggle.com/rtatman/sentiment-lexicons-for-81-languages
langCode <- read.csv("SentimentLexicons/correctedMetadata.csv", header=TRUE)$`Wikipedia.Language.Code`
negTerms <- data_frame(lang=vector(), word=vector())
posTerms <- data_frame(lang=vector(), word=vector())
for(i in 1:length(langCode)){
negTerms <- rbind(negTerms, data_frame(lang=langCode[i], word=read.delim(file=paste0("SentimentLexicons/negative_words_", langCode[i], ".txt", sep=""), header=FALSE, check.names = FALSE)))
posTerms <- rbind(posTerms, data_frame(lang=langCode[i], word=read.delim(file=paste0("SentimentLexicons/positive_words_", langCode[i], ".txt", sep=""), header=FALSE, check.names = FALSE)))
}
negTerms$sentiment <- "negative"
posTerms$sentiment <- "positive"
mySentimentLexicon <- bind_rows(negTerms, posTerms)
mySentimentLexicon <- as.data.frame(mySentimentLexicon)
# colnames(mySentimentLexicon) <- c("lang", "word", "sentiment")
# rownames(mySentimentLexicon) <- 1:nrow(mySentimentLexicon)
# Function
getScore <- function(DF, selectedLang, keyword){
DFsub <- DF %>%
filter(lang==selectedLang)
DFsub$date <- gsub("T.*", "", DFsub$created_at)
wordDF <- DFsub %>%
unnest_tokens(word, text) %>%
# anti_join(fromJSON(file=paste0("stopwords-json-master/dist/", selectedLang, ".json", sep="")))
anti_join(stopwords(language = selectedLang, source = "stopwords-iso"))
scoreDF <- wordDF %>%
inner_join(mySentimentLexicon, by=c("lang", "word")) %>%
count(status_id, sentiment) %>%
spread(sentiment, n)
scoreDF[is.na(scoreDF)] <- 0
scoreDF <- scoreDF %>%
mutate(sentiment_score=(positive-negative)/(positive+negative))
tweetScoreDF <- right_join(DFsub, scoreDF, by="status_id")
if(length(keyword)!=1){
keyword <- paste(keyword, collapse="|")
}
keywordDF <- tweetScoreDF %>%
filter(grepl(keyword, text))
return(
keywordDF %>%
group_by(date) %>%
summarize(overall_sentiment=mean(sentiment_score))
)
}
# Load all the data (too slow, Not try)
files <- list.files('/Users/mac/Desktop/Trinity/archive/')
data.df <- data.frame()
for (i in 1:length(files)) {
filepath.i <- paste('/Users/mac/Desktop/Trinity/archive/',
files[i],sep='')
data.i <- read.csv(filepath.i)
data.df <- rbind(data.df,data.i)
}
mask_score <- getEnScore(tweetMarch, "mask"); mask_score
getEnScore(tweetMarch, c("mask", "N95"))
# Sample
tweet0329 <- read.csv("/Users/mac/Desktop/Trinity/archive/2020-03-29 Coronavirus Tweets.CSV", header=TRUE)
tweet0330 <- read.csv("/Users/mac/Desktop/Trinity/archive/2020-03-30 Coronavirus Tweets.CSV", header=TRUE)
tweet0331 <- read.csv("/Users/mac/Desktop/Trinity/archive/2020-03-31 Coronavirus Tweets.CSV", header=TRUE)
tweet0401 <- read.csv("/Users/mac/Desktop/Trinity/archive/2020-04-01 Coronavirus Tweets.CSV", header=TRUE)
tweet0402 <- read.csv("/Users/mac/Desktop/Trinity/archive/2020-04-02 Coronavirus Tweets.CSV", header=TRUE)
tweet0403 <- read.csv("/Users/mac/Desktop/Trinity/archive/2020-04-03 Coronavirus Tweets.CSV", header=TRUE)
tweet0404 <- read.csv("/Users/mac/Desktop/Trinity/archive/2020-04-04 Coronavirus Tweets.CSV", header=TRUE)
tweet0405 <- read.csv("/Users/mac/Desktop/Trinity/archive/2020-04-05 Coronavirus Tweets.CSV", header=TRUE)
tweet0406 <- read.csv("/Users/mac/Desktop/Trinity/archive/2020-04-06 Coronavirus Tweets.CSV", header=TRUE)
tweet0407 <- read.csv("/Users/mac/Desktop/Trinity/archive/2020-04-07 Coronavirus Tweets.CSV", header=TRUE)
tweet0408 <- read.csv("/Users/mac/Desktop/Trinity/archive/2020-04-08 Coronavirus Tweets.CSV", header=TRUE)
tweet0409 <- read.csv("/Users/mac/Desktop/Trinity/archive/2020-04-09 Coronavirus Tweets.CSV", header=TRUE)
tweet0410 <- read.csv("/Users/mac/Desktop/Trinity/archive/2020-04-10 Coronavirus Tweets.CSV", header=TRUE)
tweet0411 <- read.csv("/Users/mac/Desktop/Trinity/archive/2020-04-11 Coronavirus Tweets.CSV", header=TRUE)
tweet0412 <- read.csv("/Users/mac/Desktop/Trinity/archive/2020-04-12 Coronavirus Tweets.CSV", header=TRUE)
tweet0413 <- read.csv("/Users/mac/Desktop/Trinity/archive/2020-04-13 Coronavirus Tweets.CSV", header=TRUE)
tweet0414 <- read.csv("/Users/mac/Desktop/Trinity/archive/2020-04-14 Coronavirus Tweets.CSV", header=TRUE)
tweet0415 <- read.csv("/Users/mac/Desktop/Trinity/archive/2020-04-15 Coronavirus Tweets.CSV", header=TRUE)
tweet0416 <- read.csv("/Users/mac/Desktop/Trinity/archive/2020-04-16 Coronavirus Tweets.CSV", header=TRUE)
tweet0417 <- read.csv("/Users/mac/Desktop/Trinity/archive/2020-04-17 Coronavirus Tweets.CSV", header=TRUE)
tweet0418 <- read.csv("/Users/mac/Desktop/Trinity/archive/2020-04-18 Coronavirus Tweets.CSV", header=TRUE)
tweet0419 <- read.csv("/Users/mac/Desktop/Trinity/archive/2020-04-19 Coronavirus Tweets.CSV", header=TRUE)
tweet0420 <- read.csv("/Users/mac/Desktop/Trinity/archive/2020-04-20 Coronavirus Tweets.CSV", header=TRUE)
tweet0421 <- read.csv("/Users/mac/Desktop/Trinity/archive/2020-04-21 Coronavirus Tweets.CSV", header=TRUE)
tweet0422 <- read.csv("/Users/mac/Desktop/Trinity/archive/2020-04-22 Coronavirus Tweets.CSV", header=TRUE)
tweet0423 <- read.csv("/Users/mac/Desktop/Trinity/archive/2020-04-23 Coronavirus Tweets.CSV", header=TRUE)
tweet0424 <- read.csv("/Users/mac/Desktop/Trinity/archive/2020-04-24 Coronavirus Tweets.CSV", header=TRUE)
tweet0425 <- read.csv("/Users/mac/Desktop/Trinity/archive/2020-04-25 Coronavirus Tweets.CSV", header=TRUE)
tweet0426 <- read.csv("/Users/mac/Desktop/Trinity/archive/2020-04-26 Coronavirus Tweets.CSV", header=TRUE)
tweet0427 <- read.csv("/Users/mac/Desktop/Trinity/archive/2020-04-27 Coronavirus Tweets.CSV", header=TRUE)
tweet0428 <- read.csv("/Users/mac/Desktop/Trinity/archive/2020-04-28 Coronavirus Tweets.CSV", header=TRUE)
tweet0429 <- read.csv("/Users/mac/Desktop/Trinity/archive/2020-04-29 Coronavirus Tweets.CSV", header=TRUE)
tweet0430 <- read.csv("/Users/mac/Desktop/Trinity/archive/2020-04-30 Coronavirus Tweets.CSV", header=TRUE)
tweet_test <- list(tweet0329, tweet0330, tweet0331,tweet0401,tweet0402,tweet0403,tweet0404,tweet0405,tweet0406,tweet0407,tweet0408,tweet0409,tweet0410,tweet0411,tweet0412,tweet0413,tweet0414,tweet0415,tweet0416,tweet0417,tweet0418,tweet0419,tweet0420,tweet0421,tweet0422,tweet0423,tweet0424,tweet0425,tweet0426,tweet0427,tweet0428,tweet0429,tweet0430)
tweet_test <- data.frame(Reduce(rbind, tweet_test))
# getEnScore(tweet_test, "mask")
mask_score <- getEnScore(tweet_test, c("mask", "N95")); mask_score
# getScore does not work well
# getScore(tweet_test, "ja", "マスク")
Visualization
# Firstly make a word frequency plot
# connect to data base
conn=dbConnect(SQLite(),dbpath)
# get twitter data with geo information
tweetsGeo=getTwitterData(conn,period = NULL)
Warning: call dbDisconnect() when finished working with a connection
# get twitter data with giving keywords and time
mask <- getTwitterTrend(conn,geoinfo = F,keywords = c('mask','N95'))
mask <- mutate(mask,
x =c(1:33) )
# making a word frequent plot of mask related data
ggplot(data = mask, aes(x = x, y = number)) +
geom_area(color="blue",fill="purple",alpha=.2)

# disconnect data base
dbDisconnect(conn)
revgeo(longitude=-77.02532657, latitude=38.93946801)
[1] "Getting geocode data from Photon: http://photon.komoot.de/reverse?lon=-77.02532657&lat=38.93946801"
[[1]]
[1] "House Number Not Found Street Not Found, City Not Found, State Not Found, Postcode Not Found, Country Not Found"
LS0tCnRpdGxlOiAiQ292aWQtVHdlZXRzIgphdXRob3I6ICJHcm91cDI6RGFucGluZyBMaXUsIEhhbyBTaGVuLCBIYW9xaSBXYW5nLCBZdXhpIFdhbmciCmRhdGU6ICIyMDIwLzEyLzIiCm91dHB1dDogaHRtbF9ub3RlYm9vawotLS0KCmBgYHtyIHNldHVwLCBpbmNsdWRlPUZBTFNFfQprbml0cjo6b3B0c19jaHVuayRzZXQoZWNobyA9IFRSVUUpCnBhY21hbjo6cF9sb2FkKHRpZHl2ZXJzZSxEQkksUlNRTGl0ZSxsdWJyaWRhdGUscnR3ZWV0LHRpZHl0ZXh0LGx1YnJpZGF0ZSxyanNvbixwbG90bHkscGFjbWFuLG1hcHMsdG1hcCxzcCxjYXJ0b2dyYW0scmV2Z2VvKQpkYnBhdGg9Ii9Vc2Vycy9tYWMvRGVza3RvcC9Db3ZpZC10d2VldHMtZW4uZGIiCmNvbm49ZGJDb25uZWN0KFNRTGl0ZSgpLGRicGF0aCkKYGBgCgojIyBEYXRhYmFzZSBwYXJ0CiMjIEdlbyBkYXRhIGFuZCBBcHJpbCBkYXRhIGNvbGxlY3Rpb25zCiogTm90ZTogVGhpcyBjaHVuayBuZWVkbid0IHJ1biBhZ2Fpbi4KYGBge3J9CiMgU2V0IFR3aXR0ZXIgZGV2ZWxvcGVyIGFjY291bnQKY3JlYXRlX3Rva2VuKGFwcD0nTVNTUC1Bbi1BdXhpbGlhcnktVG9vbCcsCiAgICAgICAgICAgICBjb25zdW1lcl9rZXk9J09SdmJBM0NFT1AwNmhpOU1IZno3eWtud1YnLAogICAgICAgICAgICAgY29uc3VtZXJfc2VjcmV0PSduQXkyUFJraVY0QVlaME52SEFGNkl3MElCRnJ0dFdNS1R1eFhiVVdONGJjWm5NcFRRUicsCiAgICAgICAgICAgICBhY2Nlc3NfdG9rZW49JzEzMjgzNzczMTM1NjI1MDkzMTMtajFpU0Z1SkxMbzNGTDc2OGpkbkhLZTFmem1jV25TJywKICAgICAgICAgICAgIGFjY2Vzc19zZWNyZXQ9J29KSWhHb1RoQk5TQlNNTFFCbzNBeFM1a2NMcnFxOHNDang2T0lQWDlOUm1QVCcpCiMgU2VsZWN0IGFsbCB0d2VldHMgaW4gQXByaWwgMjAyMAppZihGKXsKcGFzdGUoIkNSRUFURSBUQUJMRSBDb3JvbmF2aXJ1c1R3ZWV0cyIsCiAgICAgICIgQVMgU0VMRUNUICogRlJPTSBDb3JvbmF2aXJ1c1R3ZWV0c0NzdiIsCiAgICAgICIgV0hFUkUgKHN0cmZ0aW1lKCclWS0lbS0lZCAlSDolTTolUycsY3JlYXRlZF9hdCk+PSIsCiAgICAgICJzdHJmdGltZSgnJVktJW0tJWQgJUg6JU06JVMnLCcyMDIwLTAzLTI5IDAwOjAwOjAwJykpIiwKICAgICAgIiBBTkQgKHN0cmZ0aW1lKCclWS0lbS0lZCAlSDolTTolUycsY3JlYXRlZF9hdCk8PSIsCiAgICAgICJzdHJmdGltZSgnJVktJW0tJWQgJUg6JU06JVMnLCcyMDIwLTA0LTI5IDIzOjU5OjU5JykpIixzZXA9JycpJT4lCiAgZGJTZW5kUXVlcnkoY29ubiwuKQogIApBcHJpbF90d2VldD1wYXN0ZSgiU0VMRUNUIFR3ZWV0X0lEIEZST00gQ29yb25hdmlydXNUd2VldHMiLHNlcD0nJyklPiUKICBkYkdldFF1ZXJ5KGNvbm4sLikKCmZvcihpIGluIDE6Y2VpbGluZyhucm93KEFwcmlsX3R3ZWV0KS85MDAwMCkpIHsKICBybD1yYXRlX2xpbWl0KCJsb29rdXBfc3RhdHVzZXMiKQogIGlmKHJsJT4lc2VsZWN0KHJlbWFpbmluZykhPTkwMCl7CiAgICBybCU+JXNlbGVjdChyZXNldCkqNjAlPiVjZWlsaW5nKCklPiVTeXMuc2xlZXAoKQogIH0KICBhcHJpbF90d2VldD1sb29rdXBfc3RhdHVzZXMoQXByaWxfdHdlZXQkVHdlZXRfSURbKDkwMCppKTpucm93KEFwcmlsX3R3ZWV0KV0pCiAgaWYoaT09MSl7QXByaWxfdHdlZXQ9YXByaWxfdHdlZXR9ZWxzZXtBcHJpbF90d2VldD1yYmluZChBcHJpbF90d2VldCxhcHJpbF90d2VldCl9Cn0KICBBcHJpbF90d2VldCU+JQogIHNlbGVjdChzdGF0dXNfaWQsdXNlcl9pZCxzY3JlZW5fbmFtZSxjcmVhdGVkX2F0LHRleHQsaXNfcXVvdGUsCiAgICAgICAgIGlzX3JldHdlZXQsZmF2b3VyaXRlc19jb3VudCxyZXR3ZWV0X2NvdW50LGZvbGxvd2Vyc19jb3VudCwKICAgICAgICAgZnJpZW5kc19jb3VudCxsYW5nKSU+JQogIGRiV3JpdGVUYWJsZShjb25uLCdDb3JvbmF2aXJ1c1R3ZWV0cycsLikKfQojIFNlbGVjdCBhbGwgdHdlZXRzIHdpdGggZ2VvIGluZm9ybWF0aW9uIGZyb20gMjAyMDAxIHRvIDIwMjAxMQppZihGKXsgCnBhc3RlKCJDUkVBVEUgVEFCTEUgQ29yb25hdmlydXNUd2VldHNHZW8iLAogICAgICAiIEFTIFNFTEVDVCAqIEZST00gQ29yb25hdmlydXNUd2VldHNDc3YiLAogICAgICAiIFdIRVJFIEdlb2xvY2F0aW9uX2Nvb3JkaW5hdGU9J1lFUyciLHNlcD0nJyklPiUKICBkYlNlbmRRdWVyeShjb25uLC4pCgpHZW9fdHdlZXQ9cGFzdGUoIlNFTEVDVCBUd2VldF9JRCBGUk9NIENvcm9uYXZpcnVzVHdlZXRzR2VvIixzZXA9JycpJT4lCiAgZGJHZXRRdWVyeShjb25uLC4pCgoKZm9yKGkgaW4gMTpjZWlsaW5nKG5yb3coR2VvX3R3ZWV0KS85MDAwMCkpIHsKICBybD1yYXRlX2xpbWl0KCJsb29rdXBfc3RhdHVzZXMiKQogIGlmKHJsJT4lc2VsZWN0KHJlbWFpbmluZykhPTkwMCl7CiAgICBybCU+JXNlbGVjdChyZXNldCkqNjAlPiVjZWlsaW5nKCklPiVTeXMuc2xlZXAoKQogIH0KICBnZW89bG9va3VwX3N0YXR1c2VzKEdlb190d2VldCRUd2VldF9JRFsoOTAwKmkpOm5yb3coR2VvX3R3ZWV0KV0pCiAgaWYoaT09MSl7R2VvPWdlb31lbHNle0dlbz1yYmluZChHZW8sZ2VvKX0KfQoKbGF0X2xuZyhHZW8pJT4lCiAgc2VsZWN0KHN0YXR1c19pZCx1c2VyX2lkLHNjcmVlbl9uYW1lLGNyZWF0ZWRfYXQsdGV4dCxpc19xdW90ZSwKICAgICAgICAgaXNfcmV0d2VldCxmYXZvdXJpdGVzX2NvdW50LHJldHdlZXRfY291bnQsZm9sbG93ZXJzX2NvdW50LAogICAgICAgICBmcmllbmRzX2NvdW50LGxhbmcscGxhY2VfZnVsbF9uYW1lLHBsYWNlX3R5cGUsY291bnRyeV9jb2RlLAogICAgICAgICBwbGFjZV9uYW1lLGNvdW50cnksbGF0LGxuZyklPiUKICBkYldyaXRlVGFibGUoY29ubiwnQ29yb25hdmlydXNUd2VldHNHZW8nLC4pCn0KIyBEZWxldGUgaW5pdGlhbCBjb2xsZWN0aW9uIG9mIGNvdmlkIHR3ZWV0cyBjc3YgZmlsZXMgdGFibGUKIyJEUk9QIFRBQkxFIENvcm9uYXZpcnVzVHdlZXRzQ3N2IiU+JQojICBkYlNlbmRRdWVyeShjb25uLC4pCmRiRGlzY29ubmVjdChjb25uKQpgYGAKCiMjIEdldCBkYXRhIGZ1bmN0aW9uCmBgYHtyfQpnZXRUd2l0dGVyRGF0YT1mdW5jdGlvbihjb25uLGdlb2luZm89VCxrZXl3b3Jkcz1OVUxMLAogICAgICAgICAgICAgICAgICAgICAgICBwZXJpb2Q9YygnMjAyMC0wMy0yOSAwMDowMDowMCcsJzIwMjAtMDQtMzAgMjM6NTk6NTknKSl7CiAgIyBTZWxlY3QgdGFibGUgb2YgZGF0YWJhc2UgYWNjb3JkaW5nIHRvICdnZW9pbmZvJwogIGlmKGdlb2luZm8pewogICAgZ2VvaW5mb19xdWVyeT0nQ29yb25hdmlydXNUd2VldHNHZW8nCiAgfQogIGVsc2V7CiAgICBnZW9pbmZvX3F1ZXJ5PSdDb3JvbmF2aXJ1c1R3ZWV0cycKICB9CiAgIyBBZGQga2V5d29yZHMgY29uZGl0aW9ucyBhY2NvcmRpbmcgdG8gJ2tleXdvcmRzJyAKICBpZihsZW5ndGgoa2V5d29yZHM9PTApKXsKICAgIGtleXdvcmRzX3F1ZXJ5PScnCiAgfQogIGVsc2V7CiAgICBmb3IoaSBpbiAxOmxlbmd0aChrZXl3b3JkcykpewogICAgICBpZihpPT0xKXsKICAgICAgICBrZXl3b3Jkc19xdWVyeT1wYXN0ZSgiICgodGV4dCBMSUtFICclIixrZXl3b3Jkc1tpXSwiJScpIixzZXA9IiIpCiAgICAgIH0KICAgICAgZWxzZXsKICAgICAgICBrZXl3b3Jkc19xdWVyeT1rZXl3b3Jkc19xdWVyeSU+JQogICAgICAgICAgcGFzdGUoIk9SICh0ZXh0IExJS0UgJyUiLGtleXdvcmRzW2ldLCIlJykiLHNlcD0iIikKICAgICAgfQogICAgfQogICAga2V5d29yZHNfcXVlcnk9cGFzdGUoa2V5d29yZHNfcXVlcnksIikgIixzZXA9IiIpCiAgfQogICMgQWRkIHBlcmlvZCBjb25kaXRpb25zIGFjY29yZGluZyB0byAncGVyaW9kJwogIGlmKGxlbmd0aChwZXJpb2QpIT0yKXsKICAgIHBlcmlvZF9xdWVyeT0nJwogIH0KICBlbHNlewogICAgcGVyaW9kX3F1ZXJ5PXBhc3RlKCIgKHN0cmZ0aW1lKCclWS0lbS0lZCAlSDolTTolUycsY3JlYXRlZF9hdCk+PSIsCiAgICAgICAgICAgICAgICAgICAgICAgInN0cmZ0aW1lKCclWS0lbS0lZCAlSDolTTolUycsJyIscGVyaW9kWzFdLCInKSAiLAogICAgICAgICAgICAgICAgICAgICAgICJBTkQgc3RyZnRpbWUoJyVZLSVtLSVkICVIOiVNOiVTJyxjcmVhdGVkX2F0KTw9IiwKICAgICAgICAgICAgICAgICAgICAgICAic3RyZnRpbWUoJyVZLSVtLSVkICVIOiVNOiVTJywnIixwZXJpb2RbMl0sIicpKSAiLAogICAgICAgICAgICAgICAgICAgICAgIHNlcD0iIikKICB9CiAgIyBXcml0ZSBTUUwKICBpZihwZXJpb2RfcXVlcnk9PScnKXsKICAgIGlmKGtleXdvcmRzX3F1ZXJ5PT0nJyl7CiAgICAgIHF1ZXJ5PXBhc3RlKCJTRUxFQ1QgKiBGUk9NICIsZ2VvaW5mb19xdWVyeSwKICAgICAgICAgICAgICAgICAgIiBPUkRFUiBCWSBzdGF0dXNfaWQiLHNlcD0iIikKICAgIH0KICAgIGVsc2V7CiAgICAgIHF1ZXJ5PXBhc3RlKCJTRUxFQ1QgKiBGUk9NICIsZ2VvaW5mb19xdWVyeSwiICIsCiAgICAgICAgICAgICAgICAgICJXSEVSRSIsa2V5d29yZHNfcXVlcnksCiAgICAgICAgICAgICAgICAgICIgT1JERVIgQlkgc3RhdHVzX2lkIixzZXA9IiIpCiAgICB9CiAgfQogIGVsc2V7CiAgICBpZihrZXl3b3Jkc19xdWVyeT09JycpewogICAgICBxdWVyeT1wYXN0ZSgiU0VMRUNUICogRlJPTSAiLGdlb2luZm9fcXVlcnksIiAiLAogICAgICAgICAgICAgICAgICAiV0hFUkUiLHBlcmlvZF9xdWVyeSwKICAgICAgICAgICAgICAgICAgIiBPUkRFUiBCWSBzdGF0dXNfaWQiLHNlcD0iIikKICAgIH0KICAgIGVsc2V7CiAgICAgIHF1ZXJ5PXBhc3RlKCJTRUxFQ1QgKiBGUk9NICIsZ2VvaW5mb19xdWVyeSwiICIsCiAgICAgICAgICAgICAgICAgICJXSEVSRSIscGVyaW9kX3F1ZXJ5LAogICAgICAgICAgICAgICAgICAiQU5EIixrZXl3b3Jkc19xdWVyeSwKICAgICAgICAgICAgICAgICAgIiBPUkRFUiBCWSBzdGF0dXNfaWQiLHNlcD0iIikKICAgIH0KICB9CiAgIyBPYnRhaW4gRGF0YQogZGJHZXRRdWVyeShjb25uLHF1ZXJ5KSU+JW11dGF0ZShjcmVhdGVkX2F0PXltZF9obXMoY3JlYXRlZF9hdCkpCn0KYGBgCgpgYGB7cn0KZ2V0VHdpdHRlclRyZW5kPWZ1bmN0aW9uKGNvbm4sZ2VvaW5mbz1ULHRyZW5kPSdkYXknLGtleXdvcmRzPU5VTEwsCiAgICAgICAgICAgICAgICAgICAgICAgcGVyaW9kPWMoJzIwMjAtMDMtMjkgMDA6MDA6MDAnLCcyMDIwLTA0LTMwIDIzOjU5OjU5JykpewogICMgQWRkIHRyZW5kIGNjb25kaXRpb25zIGFjY29yZGluZyB0byAndHJlbmQnCiAgaWYodHJlbmQ9PSdkYXknKXsKICAgIHRyZW5kX3F1ZXJ5PWMoIiclWS0lbS0lZCciLCJkYXRlIikKICB9CiAgZWxzZXsKICAgIGlmKHRyZW5kPT0nd2VlaycpewogICAgICB0cmVuZF9xdWVyeT1jKCInJVcnIiwid2VlayIpCiAgICB9CiAgICBlbHNlewogICAgICBpZih0cmVuZD09J21vbnRoJyl7CiAgICAgICAgdHJlbmRfcXVlcnk9YygiJyVtJyIsIm1vbnRoIikKICAgICAgfQogICAgICBlbHNlewogICAgICAgIHN0b3AoIlRoZSB0cmVuZCBjYW4gb25seSBiZSAnZGF5JywgJ3dlZWsnIG9yICdtb250aCcuIikgCiAgICAgIH0KICAgIH0KICB9CiAgICAjIFNlbGVjdCB0YWJsZSBvZiBkYXRhYmFzZSBhY2NvcmRpbmcgdG8gJ2dlb2luZm8nCiAgaWYoZ2VvaW5mbyl7CiAgICBnZW9pbmZvX3F1ZXJ5PXBhc3RlKCJTRUxFQ1Qgc3RyZnRpbWUoIix0cmVuZF9xdWVyeVsxXSwKICAgICAgICAgICAgICAgICAgICAgICAgIixjcmVhdGVkX2F0KSBBUyAiLHRyZW5kX3F1ZXJ5WzJdLCIsICIsCiAgICAgICAgICAgICAgICAgICAgICAgICJjb3VudCgqKSBBUyBudW1iZXIsIHBsYWNlX25hbWUsIGNvdW50cnksICIsCiAgICAgICAgICAgICAgICAgICAgICAgICJjb3VudHJ5X2NvZGUgRlJPTSBDb3JvbmF2aXJ1c1R3ZWV0c0dlbyAiLCBzZXA9IiIpCiAgICBncm91cF9xdWVyeT1wYXN0ZSgiIEdST1VQIEJZIHN0cmZ0aW1lKCIsdHJlbmRfcXVlcnlbMV0sCiAgICAgICAgICAgICAgICAgICAgICAiLGNyZWF0ZWRfYXQpLHBsYWNlX25hbWUsY291bnRyeV9jb2RlIixzZXA9IiIpCiAgfQogIGVsc2V7CiAgICBnZW9pbmZvX3F1ZXJ5PXBhc3RlKCJTRUxFQ1Qgc3RyZnRpbWUoIix0cmVuZF9xdWVyeVsxXSwKICAgICAgICAgICAgICAgICAgICAgICAgIixjcmVhdGVkX2F0KSBBUyAiLHRyZW5kX3F1ZXJ5WzJdLCIsICIsCiAgICAgICAgICAgICAgICAgICAgICAgICJjb3VudCgqKSBBUyBudW1iZXIgRlJPTSBDb3JvbmF2aXJ1c1R3ZWV0cyIsc2VwPSIiKQogICAgZ3JvdXBfcXVlcnk9cGFzdGUoIiBHUk9VUCBCWSBzdHJmdGltZSgiLHRyZW5kX3F1ZXJ5WzFdLAogICAgICAgICAgICAgICAgICAgICAgIixjcmVhdGVkX2F0KSIsc2VwPSIiKQogIH0KICAjIEFkZCBrZXl3b3JkcyBjb25kaXRpb25zIGFjY29yZGluZyB0byAna2V5d29yZHMnIAogIGlmKGxlbmd0aChrZXl3b3Jkcz09MCkpewogICAga2V5d29yZHNfcXVlcnk9JycKICB9CiAgZWxzZXsKICAgIGZvcihpIGluIDE6bGVuZ3RoKGtleXdvcmRzKSl7CiAgICAgIGlmKGk9PTEpewogICAgICAgIGtleXdvcmRzX3F1ZXJ5PXBhc3RlKCIgKCh0ZXh0IExJS0UgJyUiLGtleXdvcmRzW2ldLCIlJykiLHNlcD0iIikKICAgICAgfQogICAgICBlbHNlewogICAgICAgIGtleXdvcmRzX3F1ZXJ5PWtleXdvcmRzX3F1ZXJ5JT4lCiAgICAgICAgICBwYXN0ZSgiT1IgKHRleHQgTElLRSAnJSIsa2V5d29yZHNbaV0sIiUnKSIsc2VwPSIiKQogICAgICB9CiAgICB9CiAgICBrZXl3b3Jkc19xdWVyeT1wYXN0ZShrZXl3b3Jkc19xdWVyeSwiKSAiLHNlcD0iIikKICB9CiAgIyBBZGQgcGVyaW9kIGNvbmRpdGlvbnMgYWNjb3JkaW5nIHRvICdwZXJpb2QnCiAgaWYobGVuZ3RoKHBlcmlvZCkhPTIpewogICAgcGVyaW9kX3F1ZXJ5PScnCiAgfQogIGVsc2V7CiAgICBwZXJpb2RfcXVlcnk9cGFzdGUoIiAoc3RyZnRpbWUoJyVZLSVtLSVkICVIOiVNOiVTJyxjcmVhdGVkX2F0KT49IiwKICAgICAgICAgICAgICAgICAgICAgICAic3RyZnRpbWUoJyVZLSVtLSVkICVIOiVNOiVTJywnIixwZXJpb2RbMV0sIicpICIsCiAgICAgICAgICAgICAgICAgICAgICAgIkFORCBzdHJmdGltZSgnJVktJW0tJWQgJUg6JU06JVMnLGNyZWF0ZWRfYXQpPD0iLAogICAgICAgICAgICAgICAgICAgICAgICJzdHJmdGltZSgnJVktJW0tJWQgJUg6JU06JVMnLCciLHBlcmlvZFsyXSwiJykpICIsCiAgICAgICAgICAgICAgICAgICAgICAgc2VwPSIiKQogIH0KICAjIFdyaXRlIFNRTAogIGlmKHBlcmlvZF9xdWVyeT09JycpewogICAgaWYoa2V5d29yZHNfcXVlcnk9PScnKXsKICAgICAgcXVlcnk9cGFzdGUoZ2VvaW5mb19xdWVyeSxncm91cF9xdWVyeSxzZXA9IiIpCiAgICB9CiAgICBlbHNlewogICAgICBxdWVyeT1wYXN0ZShnZW9pbmZvX3F1ZXJ5LCIgV0hFUkUiLGtleXdvcmRzX3F1ZXJ5LGdyb3VwX3F1ZXJ5LHNlcD0iIikKICAgIH0KICB9CiAgZWxzZXsKICAgIGlmKGtleXdvcmRzX3F1ZXJ5PT0nJyl7CiAgICAgIHF1ZXJ5PXBhc3RlKGdlb2luZm9fcXVlcnksIiBXSEVSRSIscGVyaW9kX3F1ZXJ5LGdyb3VwX3F1ZXJ5LHNlcD0iIikKICAgIH0KICAgIGVsc2V7CiAgICAgIHF1ZXJ5PXBhc3RlKGdlb2luZm9fcXVlcnksIiBXSEVSRSIscGVyaW9kX3F1ZXJ5LCJBTkQiLGtleXdvcmRzX3F1ZXJ5LAogICAgICAgICAgICAgICAgICBncm91cF9xdWVyeSxzZXA9IiIpCiAgICB9CiAgfQogICMgT2J0YWluIERhdGEKIGRiR2V0UXVlcnkoY29ubixxdWVyeSkKfQpgYGAKCiMjIEV4YW1wbGVzCmBgYHtyfQojIGNvbm5lY3QgdG8gZGF0YSBiYXNlCmNvbm49ZGJDb25uZWN0KFNRTGl0ZSgpLGRicGF0aCkKIyBnZXQgdHdpdHRlciBkYXRhIHdpdGggZ2VvIGluZm9ybWF0aW9uCnR3ZWV0c0dlbz1nZXRUd2l0dGVyRGF0YShjb25uLHBlcmlvZCA9IE5VTEwpCiMgZ2V0IHR3aXR0ZXIgbW9udGx5IGRhdGEgd2l0aCBnZW8gaW5mb3JtYXRpb24KdHdlZXRzTW9udGhseUdlbz1nZXRUd2l0dGVyVHJlbmQoY29ubix0cmVuZD0nbW9udGgnLHBlcmlvZD1OVUxMKQojIGdldCB0d2l0dGVyIGRhdGEgd2l0aCBnaXZpbmcga2V5d29yZHMKdHdlZXRzPWdldFR3aXR0ZXJEYXRhKGNvbm4sZ2VvaW5mbyA9IEYsa2V5d29yZHMgPSBjKCdtYXNrJywnTjk1Jywn5Y+j572pJykpCiMgZ2V0IHR3aXR0ZXIgZGFpbHkgdHJlbmRzIGdpdmluZyBrZXl3b3Jkcwp0d2VldHNEYWlseT1nZXRUd2l0dGVyVHJlbmQoY29ubixnZW9pbmZvID0gRixrZXl3b3JkcyA9IGMoJ21hc2snLCdOOTUnLCflj6PnvaknKSkKIyBkaXNjb25uZWN0IGRhdGEgYmFzZQpkYkRpc2Nvbm5lY3QoY29ubikKYGBgCgojIFRoZSBzZW50aW1lbnQgYW5hbHlzaXMgcGFydApgYGB7cn0KIyBGb3IgRW5nbGlzaCB0d2VldHMgaW4gVW5pdGVkIFN0YXRlcwpnZXRFblNjb3JlIDwtIGZ1bmN0aW9uKERGLCBrZXl3b3JkKXsKICBERnN1YiA8LSBERiAlPiUKICAgIGZpbHRlcihsYW5nPT0iZW4iJmNvdW50cnlfY29kZT09IlVTIikKICAKICBERnN1YiRkYXRlIDwtIGdzdWIoIlQuKiIsICIiLCBERnN1YiRjcmVhdGVkX2F0KQogIAogIHdvcmRERiA8LSBERnN1YiAlPiUKICAgIHVubmVzdF90b2tlbnMod29yZCwgdGV4dCkgJT4lCiAgICBhbnRpX2pvaW4oc3RvcF93b3JkcykgCiAgCiAgc2NvcmVERiA8LSB3b3JkREYgJT4lCiAgICBpbm5lcl9qb2luKGdldF9zZW50aW1lbnRzKCJiaW5nIikpICU+JQogICAgY291bnQoc3RhdHVzX2lkLCBzZW50aW1lbnQpICU+JQogICAgc3ByZWFkKHNlbnRpbWVudCwgbikKICBzY29yZURGW2lzLm5hKHNjb3JlREYpXSA8LSAwCiAgc2NvcmVERiA8LSBzY29yZURGICU+JQogICAgbXV0YXRlKHNlbnRpbWVudF9zY29yZT0ocG9zaXRpdmUtbmVnYXRpdmUpLyhwb3NpdGl2ZStuZWdhdGl2ZSkpIAogIAogIHR3ZWV0U2NvcmVERiA8LSByaWdodF9qb2luKERGc3ViLCBzY29yZURGLCBieT0ic3RhdHVzX2lkIikKICAKICBpZihsZW5ndGgoa2V5d29yZCkhPTEpewogICAga2V5d29yZCA8LSBwYXN0ZShrZXl3b3JkLCBjb2xsYXBzZT0ifCIpCiAgfQogIGtleXdvcmRERiA8LSB0d2VldFNjb3JlREYgJT4lCiAgICBmaWx0ZXIoZ3JlcGwoa2V5d29yZCwgdGV4dCkpCiAgCiAgcmV0dXJuKAogICAga2V5d29yZERGICU+JQogICAgICBncm91cF9ieShkYXRlKSAlPiUKICAgICAgc3VtbWFyaXplKG92ZXJhbGxfc2VudGltZW50PW1lYW4oc2VudGltZW50X3Njb3JlKSkKICApCn0KCmBgYAoKYGBge3J9CiMgRm9yIGFsbCBsYW5ndWFnZXMKIyBDcmVhdGUgc2VudGltZW50IGxleGljb24gZGljdGlvbmFyeQojIGh0dHBzOi8vd3d3LmthZ2dsZS5jb20vcnRhdG1hbi9zZW50aW1lbnQtbGV4aWNvbnMtZm9yLTgxLWxhbmd1YWdlcwoKbGFuZ0NvZGUgPC0gcmVhZC5jc3YoIlNlbnRpbWVudExleGljb25zL2NvcnJlY3RlZE1ldGFkYXRhLmNzdiIsIGhlYWRlcj1UUlVFKSRgV2lraXBlZGlhLkxhbmd1YWdlLkNvZGVgCgpuZWdUZXJtcyA8LSBkYXRhX2ZyYW1lKGxhbmc9dmVjdG9yKCksIHdvcmQ9dmVjdG9yKCkpCnBvc1Rlcm1zIDwtIGRhdGFfZnJhbWUobGFuZz12ZWN0b3IoKSwgd29yZD12ZWN0b3IoKSkKCmZvcihpIGluIDE6bGVuZ3RoKGxhbmdDb2RlKSl7CiAgbmVnVGVybXMgPC0gcmJpbmQobmVnVGVybXMsIGRhdGFfZnJhbWUobGFuZz1sYW5nQ29kZVtpXSwgd29yZD1yZWFkLmRlbGltKGZpbGU9cGFzdGUwKCJTZW50aW1lbnRMZXhpY29ucy9uZWdhdGl2ZV93b3Jkc18iLCBsYW5nQ29kZVtpXSwgIi50eHQiLCBzZXA9IiIpLCBoZWFkZXI9RkFMU0UsIGNoZWNrLm5hbWVzID0gRkFMU0UpKSkKICBwb3NUZXJtcyA8LSByYmluZChwb3NUZXJtcywgZGF0YV9mcmFtZShsYW5nPWxhbmdDb2RlW2ldLCB3b3JkPXJlYWQuZGVsaW0oZmlsZT1wYXN0ZTAoIlNlbnRpbWVudExleGljb25zL3Bvc2l0aXZlX3dvcmRzXyIsIGxhbmdDb2RlW2ldLCAiLnR4dCIsIHNlcD0iIiksIGhlYWRlcj1GQUxTRSwgY2hlY2submFtZXMgPSBGQUxTRSkpKQp9Cm5lZ1Rlcm1zJHNlbnRpbWVudCA8LSAibmVnYXRpdmUiCnBvc1Rlcm1zJHNlbnRpbWVudCA8LSAicG9zaXRpdmUiCgpteVNlbnRpbWVudExleGljb24gPC0gYmluZF9yb3dzKG5lZ1Rlcm1zLCBwb3NUZXJtcykKbXlTZW50aW1lbnRMZXhpY29uIDwtIGFzLmRhdGEuZnJhbWUobXlTZW50aW1lbnRMZXhpY29uKQoKIyBjb2xuYW1lcyhteVNlbnRpbWVudExleGljb24pIDwtIGMoImxhbmciLCAid29yZCIsICJzZW50aW1lbnQiKQojIHJvd25hbWVzKG15U2VudGltZW50TGV4aWNvbikgPC0gMTpucm93KG15U2VudGltZW50TGV4aWNvbikKCiMgRnVuY3Rpb24KZ2V0U2NvcmUgPC0gZnVuY3Rpb24oREYsIHNlbGVjdGVkTGFuZywga2V5d29yZCl7CiAgREZzdWIgPC0gREYgJT4lCiAgICBmaWx0ZXIobGFuZz09c2VsZWN0ZWRMYW5nKQogIAogIERGc3ViJGRhdGUgPC0gZ3N1YigiVC4qIiwgIiIsIERGc3ViJGNyZWF0ZWRfYXQpCiAgCiAgd29yZERGIDwtIERGc3ViICU+JQogICAgdW5uZXN0X3Rva2Vucyh3b3JkLCB0ZXh0KSAlPiUKICAgICMgYW50aV9qb2luKGZyb21KU09OKGZpbGU9cGFzdGUwKCJzdG9wd29yZHMtanNvbi1tYXN0ZXIvZGlzdC8iLCBzZWxlY3RlZExhbmcsICIuanNvbiIsIHNlcD0iIikpKQogICAgYW50aV9qb2luKHN0b3B3b3JkcyhsYW5ndWFnZSA9IHNlbGVjdGVkTGFuZywgc291cmNlID0gInN0b3B3b3Jkcy1pc28iKSkKICAKICBzY29yZURGIDwtIHdvcmRERiAlPiUKICAgIGlubmVyX2pvaW4obXlTZW50aW1lbnRMZXhpY29uLCBieT1jKCJsYW5nIiwgIndvcmQiKSkgJT4lCiAgICBjb3VudChzdGF0dXNfaWQsIHNlbnRpbWVudCkgJT4lCiAgICBzcHJlYWQoc2VudGltZW50LCBuKQogIHNjb3JlREZbaXMubmEoc2NvcmVERildIDwtIDAKICBzY29yZURGIDwtIHNjb3JlREYgJT4lCiAgICBtdXRhdGUoc2VudGltZW50X3Njb3JlPShwb3NpdGl2ZS1uZWdhdGl2ZSkvKHBvc2l0aXZlK25lZ2F0aXZlKSkgCiAgCiAgdHdlZXRTY29yZURGIDwtIHJpZ2h0X2pvaW4oREZzdWIsIHNjb3JlREYsIGJ5PSJzdGF0dXNfaWQiKQogIAogIGlmKGxlbmd0aChrZXl3b3JkKSE9MSl7CiAgICBrZXl3b3JkIDwtIHBhc3RlKGtleXdvcmQsIGNvbGxhcHNlPSJ8IikKICB9CiAga2V5d29yZERGIDwtIHR3ZWV0U2NvcmVERiAlPiUKICAgIGZpbHRlcihncmVwbChrZXl3b3JkLCB0ZXh0KSkKICAKICByZXR1cm4oCiAgICBrZXl3b3JkREYgJT4lCiAgICAgIGdyb3VwX2J5KGRhdGUpICU+JQogICAgICBzdW1tYXJpemUob3ZlcmFsbF9zZW50aW1lbnQ9bWVhbihzZW50aW1lbnRfc2NvcmUpKQogICkKfQpgYGAKCmBgYHtyfQojIExvYWQgYWxsIHRoZSBkYXRhICh0b28gc2xvdywgTm90IHRyeSkKZmlsZXMgPC0gbGlzdC5maWxlcygnL1VzZXJzL21hYy9EZXNrdG9wL1RyaW5pdHkvYXJjaGl2ZS8nKQpkYXRhLmRmIDwtIGRhdGEuZnJhbWUoKQpmb3IgKGkgaW4gMTpsZW5ndGgoZmlsZXMpKSB7CiAgZmlsZXBhdGguaSA8LSBwYXN0ZSgnL1VzZXJzL21hYy9EZXNrdG9wL1RyaW5pdHkvYXJjaGl2ZS8nLAogICAgICAgICAgICAgICAgICAgICAgZmlsZXNbaV0sc2VwPScnKQogIGRhdGEuaSA8LSByZWFkLmNzdihmaWxlcGF0aC5pKQogIGRhdGEuZGYgPC0gcmJpbmQoZGF0YS5kZixkYXRhLmkpCn0KCm1hc2tfc2NvcmUgPC0gZ2V0RW5TY29yZSh0d2VldE1hcmNoLCAibWFzayIpOyBtYXNrX3Njb3JlCmdldEVuU2NvcmUodHdlZXRNYXJjaCwgYygibWFzayIsICJOOTUiKSkKCiMgU2FtcGxlCnR3ZWV0MDMyOSA8LSByZWFkLmNzdigiL1VzZXJzL21hYy9EZXNrdG9wL1RyaW5pdHkvYXJjaGl2ZS8yMDIwLTAzLTI5IENvcm9uYXZpcnVzIFR3ZWV0cy5DU1YiLCBoZWFkZXI9VFJVRSkKdHdlZXQwMzMwIDwtIHJlYWQuY3N2KCIvVXNlcnMvbWFjL0Rlc2t0b3AvVHJpbml0eS9hcmNoaXZlLzIwMjAtMDMtMzAgQ29yb25hdmlydXMgVHdlZXRzLkNTViIsIGhlYWRlcj1UUlVFKQp0d2VldDAzMzEgPC0gcmVhZC5jc3YoIi9Vc2Vycy9tYWMvRGVza3RvcC9UcmluaXR5L2FyY2hpdmUvMjAyMC0wMy0zMSBDb3JvbmF2aXJ1cyBUd2VldHMuQ1NWIiwgaGVhZGVyPVRSVUUpCnR3ZWV0MDQwMSA8LSByZWFkLmNzdigiL1VzZXJzL21hYy9EZXNrdG9wL1RyaW5pdHkvYXJjaGl2ZS8yMDIwLTA0LTAxIENvcm9uYXZpcnVzIFR3ZWV0cy5DU1YiLCBoZWFkZXI9VFJVRSkKdHdlZXQwNDAyIDwtIHJlYWQuY3N2KCIvVXNlcnMvbWFjL0Rlc2t0b3AvVHJpbml0eS9hcmNoaXZlLzIwMjAtMDQtMDIgQ29yb25hdmlydXMgVHdlZXRzLkNTViIsIGhlYWRlcj1UUlVFKQp0d2VldDA0MDMgPC0gcmVhZC5jc3YoIi9Vc2Vycy9tYWMvRGVza3RvcC9UcmluaXR5L2FyY2hpdmUvMjAyMC0wNC0wMyBDb3JvbmF2aXJ1cyBUd2VldHMuQ1NWIiwgaGVhZGVyPVRSVUUpCnR3ZWV0MDQwNCA8LSByZWFkLmNzdigiL1VzZXJzL21hYy9EZXNrdG9wL1RyaW5pdHkvYXJjaGl2ZS8yMDIwLTA0LTA0IENvcm9uYXZpcnVzIFR3ZWV0cy5DU1YiLCBoZWFkZXI9VFJVRSkKdHdlZXQwNDA1IDwtIHJlYWQuY3N2KCIvVXNlcnMvbWFjL0Rlc2t0b3AvVHJpbml0eS9hcmNoaXZlLzIwMjAtMDQtMDUgQ29yb25hdmlydXMgVHdlZXRzLkNTViIsIGhlYWRlcj1UUlVFKQp0d2VldDA0MDYgPC0gcmVhZC5jc3YoIi9Vc2Vycy9tYWMvRGVza3RvcC9UcmluaXR5L2FyY2hpdmUvMjAyMC0wNC0wNiBDb3JvbmF2aXJ1cyBUd2VldHMuQ1NWIiwgaGVhZGVyPVRSVUUpCnR3ZWV0MDQwNyA8LSByZWFkLmNzdigiL1VzZXJzL21hYy9EZXNrdG9wL1RyaW5pdHkvYXJjaGl2ZS8yMDIwLTA0LTA3IENvcm9uYXZpcnVzIFR3ZWV0cy5DU1YiLCBoZWFkZXI9VFJVRSkKdHdlZXQwNDA4IDwtIHJlYWQuY3N2KCIvVXNlcnMvbWFjL0Rlc2t0b3AvVHJpbml0eS9hcmNoaXZlLzIwMjAtMDQtMDggQ29yb25hdmlydXMgVHdlZXRzLkNTViIsIGhlYWRlcj1UUlVFKQp0d2VldDA0MDkgPC0gcmVhZC5jc3YoIi9Vc2Vycy9tYWMvRGVza3RvcC9UcmluaXR5L2FyY2hpdmUvMjAyMC0wNC0wOSBDb3JvbmF2aXJ1cyBUd2VldHMuQ1NWIiwgaGVhZGVyPVRSVUUpCnR3ZWV0MDQxMCA8LSByZWFkLmNzdigiL1VzZXJzL21hYy9EZXNrdG9wL1RyaW5pdHkvYXJjaGl2ZS8yMDIwLTA0LTEwIENvcm9uYXZpcnVzIFR3ZWV0cy5DU1YiLCBoZWFkZXI9VFJVRSkKdHdlZXQwNDExIDwtIHJlYWQuY3N2KCIvVXNlcnMvbWFjL0Rlc2t0b3AvVHJpbml0eS9hcmNoaXZlLzIwMjAtMDQtMTEgQ29yb25hdmlydXMgVHdlZXRzLkNTViIsIGhlYWRlcj1UUlVFKQp0d2VldDA0MTIgPC0gcmVhZC5jc3YoIi9Vc2Vycy9tYWMvRGVza3RvcC9UcmluaXR5L2FyY2hpdmUvMjAyMC0wNC0xMiBDb3JvbmF2aXJ1cyBUd2VldHMuQ1NWIiwgaGVhZGVyPVRSVUUpCnR3ZWV0MDQxMyA8LSByZWFkLmNzdigiL1VzZXJzL21hYy9EZXNrdG9wL1RyaW5pdHkvYXJjaGl2ZS8yMDIwLTA0LTEzIENvcm9uYXZpcnVzIFR3ZWV0cy5DU1YiLCBoZWFkZXI9VFJVRSkKdHdlZXQwNDE0IDwtIHJlYWQuY3N2KCIvVXNlcnMvbWFjL0Rlc2t0b3AvVHJpbml0eS9hcmNoaXZlLzIwMjAtMDQtMTQgQ29yb25hdmlydXMgVHdlZXRzLkNTViIsIGhlYWRlcj1UUlVFKQp0d2VldDA0MTUgPC0gcmVhZC5jc3YoIi9Vc2Vycy9tYWMvRGVza3RvcC9UcmluaXR5L2FyY2hpdmUvMjAyMC0wNC0xNSBDb3JvbmF2aXJ1cyBUd2VldHMuQ1NWIiwgaGVhZGVyPVRSVUUpCnR3ZWV0MDQxNiA8LSByZWFkLmNzdigiL1VzZXJzL21hYy9EZXNrdG9wL1RyaW5pdHkvYXJjaGl2ZS8yMDIwLTA0LTE2IENvcm9uYXZpcnVzIFR3ZWV0cy5DU1YiLCBoZWFkZXI9VFJVRSkKdHdlZXQwNDE3IDwtIHJlYWQuY3N2KCIvVXNlcnMvbWFjL0Rlc2t0b3AvVHJpbml0eS9hcmNoaXZlLzIwMjAtMDQtMTcgQ29yb25hdmlydXMgVHdlZXRzLkNTViIsIGhlYWRlcj1UUlVFKQp0d2VldDA0MTggPC0gcmVhZC5jc3YoIi9Vc2Vycy9tYWMvRGVza3RvcC9UcmluaXR5L2FyY2hpdmUvMjAyMC0wNC0xOCBDb3JvbmF2aXJ1cyBUd2VldHMuQ1NWIiwgaGVhZGVyPVRSVUUpCnR3ZWV0MDQxOSA8LSByZWFkLmNzdigiL1VzZXJzL21hYy9EZXNrdG9wL1RyaW5pdHkvYXJjaGl2ZS8yMDIwLTA0LTE5IENvcm9uYXZpcnVzIFR3ZWV0cy5DU1YiLCBoZWFkZXI9VFJVRSkKdHdlZXQwNDIwIDwtIHJlYWQuY3N2KCIvVXNlcnMvbWFjL0Rlc2t0b3AvVHJpbml0eS9hcmNoaXZlLzIwMjAtMDQtMjAgQ29yb25hdmlydXMgVHdlZXRzLkNTViIsIGhlYWRlcj1UUlVFKQp0d2VldDA0MjEgPC0gcmVhZC5jc3YoIi9Vc2Vycy9tYWMvRGVza3RvcC9UcmluaXR5L2FyY2hpdmUvMjAyMC0wNC0yMSBDb3JvbmF2aXJ1cyBUd2VldHMuQ1NWIiwgaGVhZGVyPVRSVUUpCnR3ZWV0MDQyMiA8LSByZWFkLmNzdigiL1VzZXJzL21hYy9EZXNrdG9wL1RyaW5pdHkvYXJjaGl2ZS8yMDIwLTA0LTIyIENvcm9uYXZpcnVzIFR3ZWV0cy5DU1YiLCBoZWFkZXI9VFJVRSkKdHdlZXQwNDIzIDwtIHJlYWQuY3N2KCIvVXNlcnMvbWFjL0Rlc2t0b3AvVHJpbml0eS9hcmNoaXZlLzIwMjAtMDQtMjMgQ29yb25hdmlydXMgVHdlZXRzLkNTViIsIGhlYWRlcj1UUlVFKQp0d2VldDA0MjQgPC0gcmVhZC5jc3YoIi9Vc2Vycy9tYWMvRGVza3RvcC9UcmluaXR5L2FyY2hpdmUvMjAyMC0wNC0yNCBDb3JvbmF2aXJ1cyBUd2VldHMuQ1NWIiwgaGVhZGVyPVRSVUUpCnR3ZWV0MDQyNSA8LSByZWFkLmNzdigiL1VzZXJzL21hYy9EZXNrdG9wL1RyaW5pdHkvYXJjaGl2ZS8yMDIwLTA0LTI1IENvcm9uYXZpcnVzIFR3ZWV0cy5DU1YiLCBoZWFkZXI9VFJVRSkKdHdlZXQwNDI2IDwtIHJlYWQuY3N2KCIvVXNlcnMvbWFjL0Rlc2t0b3AvVHJpbml0eS9hcmNoaXZlLzIwMjAtMDQtMjYgQ29yb25hdmlydXMgVHdlZXRzLkNTViIsIGhlYWRlcj1UUlVFKQp0d2VldDA0MjcgPC0gcmVhZC5jc3YoIi9Vc2Vycy9tYWMvRGVza3RvcC9UcmluaXR5L2FyY2hpdmUvMjAyMC0wNC0yNyBDb3JvbmF2aXJ1cyBUd2VldHMuQ1NWIiwgaGVhZGVyPVRSVUUpCnR3ZWV0MDQyOCA8LSByZWFkLmNzdigiL1VzZXJzL21hYy9EZXNrdG9wL1RyaW5pdHkvYXJjaGl2ZS8yMDIwLTA0LTI4IENvcm9uYXZpcnVzIFR3ZWV0cy5DU1YiLCBoZWFkZXI9VFJVRSkKdHdlZXQwNDI5IDwtIHJlYWQuY3N2KCIvVXNlcnMvbWFjL0Rlc2t0b3AvVHJpbml0eS9hcmNoaXZlLzIwMjAtMDQtMjkgQ29yb25hdmlydXMgVHdlZXRzLkNTViIsIGhlYWRlcj1UUlVFKQp0d2VldDA0MzAgPC0gcmVhZC5jc3YoIi9Vc2Vycy9tYWMvRGVza3RvcC9UcmluaXR5L2FyY2hpdmUvMjAyMC0wNC0zMCBDb3JvbmF2aXJ1cyBUd2VldHMuQ1NWIiwgaGVhZGVyPVRSVUUpCgp0d2VldF90ZXN0IDwtIGxpc3QodHdlZXQwMzI5LCB0d2VldDAzMzAsIHR3ZWV0MDMzMSx0d2VldDA0MDEsdHdlZXQwNDAyLHR3ZWV0MDQwMyx0d2VldDA0MDQsdHdlZXQwNDA1LHR3ZWV0MDQwNix0d2VldDA0MDcsdHdlZXQwNDA4LHR3ZWV0MDQwOSx0d2VldDA0MTAsdHdlZXQwNDExLHR3ZWV0MDQxMix0d2VldDA0MTMsdHdlZXQwNDE0LHR3ZWV0MDQxNSx0d2VldDA0MTYsdHdlZXQwNDE3LHR3ZWV0MDQxOCx0d2VldDA0MTksdHdlZXQwNDIwLHR3ZWV0MDQyMSx0d2VldDA0MjIsdHdlZXQwNDIzLHR3ZWV0MDQyNCx0d2VldDA0MjUsdHdlZXQwNDI2LHR3ZWV0MDQyNyx0d2VldDA0MjgsdHdlZXQwNDI5LHR3ZWV0MDQzMCkKdHdlZXRfdGVzdCA8LSBkYXRhLmZyYW1lKFJlZHVjZShyYmluZCwgdHdlZXRfdGVzdCkpCiMgZ2V0RW5TY29yZSh0d2VldF90ZXN0LCAibWFzayIpCm1hc2tfc2NvcmUgPC0gZ2V0RW5TY29yZSh0d2VldF90ZXN0LCBjKCJtYXNrIiwgIk45NSIpKTsgbWFza19zY29yZQojIGdldFNjb3JlIGRvZXMgbm90IHdvcmsgd2VsbAojIGdldFNjb3JlKHR3ZWV0X3Rlc3QsICJqYSIsICLjg57jgrnjgq8iKQpgYGAKClZpc3VhbGl6YXRpb24KCmBgYHtyfQoKIyBGaXJzdGx5IG1ha2UgYSB3b3JkIGZyZXF1ZW5jeSBwbG90CiMgY29ubmVjdCB0byBkYXRhIGJhc2UKY29ubj1kYkNvbm5lY3QoU1FMaXRlKCksZGJwYXRoKQojIGdldCB0d2l0dGVyIGRhdGEgd2l0aCBnZW8gaW5mb3JtYXRpb24KdHdlZXRzR2VvPWdldFR3aXR0ZXJEYXRhKGNvbm4scGVyaW9kID0gTlVMTCkKIyBnZXQgdHdpdHRlciBkYXRhIHdpdGggZ2l2aW5nIGtleXdvcmRzIGFuZCB0aW1lCm1hc2sgPC0gZ2V0VHdpdHRlclRyZW5kKGNvbm4sZ2VvaW5mbyA9IEYsa2V5d29yZHMgPSBjKCdtYXNrJywnTjk1JykpCm1hc2sgPC0gbXV0YXRlKG1hc2ssCiAgICAgICB4ID1jKDE6MzMpICkKIyBtYWtpbmcgYSB3b3JkIGZyZXF1ZW50IHBsb3Qgb2YgbWFzayByZWxhdGVkIGRhdGEKZ2dwbG90KGRhdGEgPSBtYXNrLCBhZXMoeCA9IHgsIHkgPSBudW1iZXIpKSArCiAgZ2VvbV9hcmVhKGNvbG9yPSJibHVlIixmaWxsPSJwdXJwbGUiLGFscGhhPS4yKQoKIyBkaXNjb25uZWN0IGRhdGEgYmFzZQpkYkRpc2Nvbm5lY3QoY29ubikKCmBgYAoKYGBge3J9CgojIGxvYWQgc3ByZWFkIGRhdGEKZGFpbHkgPC0gcmVhZC5jc3YoZmlsZT0gIi9Vc2Vycy9tYWMvRGVza3RvcC9UcmluaXR5L3VzX2NvdmlkMTlfZGFpbHkuY3N2IiwgaGVhZGVyPVRSVUUpCnNwcmVhZF9kYXRhIDwtIHNlbGVjdChkYWlseSxkYXRlLGhvc3BpdGFsaXplZEN1bXVsYXRpdmUsZGVhdGgsZGVhdGhJbmNyZWFzZSxuZWdhdGl2ZUluY3JlYXNlKSU+JQogIG11dGF0ZShkYXRlX25ldz15bWQoZGF0ZSkpJT4lCiAgYXJyYW5nZShkYWlseSwgZGVzYyhkYXRlX25ldykpCnNwcmVhZF9kYXRhIDwtIHNwcmVhZF9kYXRhWzY4OjEwMCxdCgoKIyB1c2luZyBwbG90bHkgcGFja2FnZSB0byBtYWtlIGEgcGxvdCB0aGF0IGJvdGggaGF2ZSBkZWF0aCwgc2VudGltZW50IGFuZCBmcmVxdWVuY3kKZGVhdGggPC0gc3ByZWFkX2RhdGFbLDNdCmZyZXF1ZW5jeSA8LSBtYXNrJG51bWJlcgpzZW50aW1lbnQgPC0gbWFza19zY29yZSRvdmVyYWxsX3NlbnRpbWVudApkYXRlIDwtc3ByZWFkX2RhdGEkZGF0ZV9uZXcKZGF0ZSA8LSAxOjMzCmRhdGEgPC0gZGF0YS5mcmFtZShkYXRlLCBkZWF0aCwgZnJlcXVlbmN5KSAjICwgc2VudGltZW50KQoKZmlnIDwtIHBsb3RfbHkoZGF0YSwgeCA9IH5kYXRlLCB5ID0gfmRlYXRoLCBuYW1lID0gJ2RlYXRoJywgdHlwZSA9ICdzY2F0dGVyJywgbW9kZSA9ICdsaW5lcyttYXJrZXJzJykgCmZpZyA8LSBmaWcgJT4lIAogIGFkZF90cmFjZSh5ID0gfmZyZXF1ZW5jeSwgbmFtZSA9ICdmcmVxdWVuY3knLCBtb2RlID0gJ2xpbmVzK21hcmtlcnMnKSAKCmZpZyA8LSBmaWcgJT4lIAogIGFkZF90cmFjZSh5ID0gfnNlbnRpbWVudCwgbmFtZSA9ICdzZW50aW1lbnQnLCBtb2RlID0gJ2xpbmVzK21hcmtlcnMnKQpmaWcKYGBgCgoKYGBge3J9CgojIEZvciB0aGUgZ2VvIHBsb3QKc3RhdGVzIDwtIGMoInRleGFzIiwib2tsYWhvbWEiLCJrYW5zYXMiLCJsb3Vpc2lhbmEiLCJhcmthbnNhcyIsIm1pc3NvdXJpIiwiaW93YSIsCiJ3aXNjb25zaW4iLCJtaWNoaWdhbiIsImlsbGlub2lzIiwiaW5kaWFuYSIsIm9oaW8iLCJrZW50dWNreSIsInRlbm5lc3NlZSIsCiJhbGFiYW1hIiwibWlzc2lzc2lwcGkiLCJmbG9yaWRhIiwiZ2VvcmdpYSIsInNvdXRoIGNhcm9saW5hIiwibm9ydGggY2Fyb2xpbmEiLAoidmlyZ2luaWEiLCJ3ZXN0IHZpcmdpbmlhIiwibWFyeWxhbmQiLCJkZWxhd2FyZSIsInBlbm5zeWx2YW5pYSIsIm5ldyBqZXJzZXkiLAoibmV3IHlvcmsiLCJjb25uZWN0aWN1dCIsInJob2RlIGlzbGFuZCIsIm1hc3NhY2h1c2V0dHMiLCJ2ZXJtb250IiwKIm5ldyBoYW1wc2hpcmUiLCJtYWluZSIpCgojdHVybiBkYXRhIGZyb20gdGhlIG1hcHMgcGFja2FnZSBpbiB0byBhIGRhdGEgZnJhbWUgc3VpdGFibGUgZm9yIHBsb3R0aW5nIHdpdGggZ2dwbG90MgptYXBfc3RhdGVzIDwtIG1hcF9kYXRhKCJjb3VudHkiLCBzdGF0ZXMpCiMgVG8gZHJhdyB0aGUgYm9yZGVyLWJ5IGdyb3VwIDEwCm1hcF9zdGF0ZXNfYm9yZGVyIDwtIG1hcF9kYXRhKCJzdGF0ZSIsc3RhdGVzKQoKCnZpZXcodHdlZXRzR2VvKQp0d2VldHNHZW8gPC0gdHdlZXRzR2VvICU+JQogIGdyb3VwX2J5KHBsYWNlX25hbWUpJT4lCiAgbXV0YXRlKHN1bSA9IG4oKSwKICAgICAgICAgbG9uZz1sbmcpCnN1bW1hcnkodHdlZXRzR2VvJHN1bSkKCgojZGl2aWRlIHN1bSBvZiBwcmVjaXAgaW50byA0IHBhcnRzIHdoaWNoIGlzIAp0d2VldHNHZW8kY3V0IDwtIGN1dCh0d2VldHNHZW8kc3VtLAogICAgICAgICAgICAgICAgICAgICAgYnJlYWtzPWMoMCwzNiwxMDQ1LDExMjIsOTAwMCksCiAgICAgICAgICAgICAgICAgICAgICBpbmNsdWRlLmxvd2VzdCA9IFQpCgp0d2VldHNHZW9fRW4gPC0gZmlsdGVyKHR3ZWV0c0dlbywgY291bnRyeV9jb2RlID09ICdVUycpClZpZXcodHdlZXRzR2VvX0VuKQoKI3VzZSAnbWVyZ2UnIHRvIGNvbWJpbmUgdHdvIGRhdGEgZnJhbWVzLgp0d2VldHNHZW9fRW5fMSA8LSBtZXJnZSh0d2VldHNHZW9fRW4sbWFwX3N0YXRlcywgYnk9YygibG9uZyIsImxhdCIpKQoKcmV2Z2VvKGxvbmdpdHVkZT0tNzcuMDI1MzI2NTcsIGxhdGl0dWRlPTM4LjkzOTQ2ODAxKQojIE1ha2UgdGhlIGdlbyBwbG90IGluIGdncGxvdAp0d2VldHNHZW9fcGxvdCA8LSBnZ3Bsb3QoKSsKICBnZW9tX3BvbHlnb24odHdlZXRzR2VvX0VuLCBtYXBwaW5nPWFlcyh4PWxuZywgeT1sYXQpLCBmaWxsPWN1dCkrCiAgI2Rpc3BsYXkgZGlzY3JldGUgdmFsdWVzIG9uIGEgbWFwCiAgc2NhbGVfZmlsbF9icmV3ZXIocGFsZXR0ZT0iQmx1ZXMiKSsKICAjY2hhbmdlIHRoZSBuYW1lIG9mIHgsIHksIGFuZCB0aXRsZQogIHhsYWIoIkxvbmd0aXR1ZGUiKSt5bGFiKCJMYXRpdHVkZSIpK2dndGl0bGUoInR3ZWV0c0dlbyIpKwogICNhZGQgbWFya3MKICBsYWJzKGZpbGw9Im51bWJlciBvZiB0d2VldHMiKSsKICB0aGVtZShwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KGhqdXN0ID0gMC41LCBzaXplID0gMTgpKQoKdHdlZXRzR2VvX3Bsb3QKCgpkYXRhKFdvcmxkKQpjbGFzcyhXb3JsZCkKV29ybGQgJT4lIGFzLmRhdGEuZnJhbWUoKSAlPiUgaGVhZCgpCnRtX3NoYXBlKFdvcmxkKSsKICB0bV9wb2x5Z29ucygiSFBJIiwgcGFsZXR0ZT0iLUJsdWVzIiwgY29udHJhc3Q9LjcsIGlkPSJuYW1lIiwgdGl0bGU9IkhhcHBpbmVzcyBJbmRleCIpCmBgYAoKCgoKCg==